package de.dailab.graphworld.ontology;


import java.util.ArrayList;

import de.dailab.jiactng.agentcore.knowledge.IFact;
import de.dailab.jiactng.agentcore.ontology.IAgentDescription;


public class Node implements IFact{
	
	private static final long serialVersionUID = 7268880400709544017L;

	/**
	 * The ID of a graph node
	 */
	public Integer id;
	
	// coordinates to draw
	private Integer x;
	private Integer y;
	
	private Integer gold;
	
	private ArrayList<Edge> edges;
	
	public ArrayList<IAgentDescription> agents = new ArrayList<IAgentDescription>();
	
	/**
	 * basic constructor, creates a node with a given id
	 * this constructor should only be used for comparism purposes since the node cannot be drawn due to the lack of positional coordinates
	 * @param id the id of the node
	 */
	public Node (Integer id){
		this.id = id;
		this.x = null;
		this.y = null;
		this.gold = 0;
		this.edges = null;
	}
	
	/**
	 * standard constructor, creates new node which doesn't contain any data or gold
	 * @param id a node has to have an id
	 */
	public Node (Integer id, Integer x, Integer y, Integer gold){
		this.id = id;
		this.x = x;
		this.y = y;
		this.gold = gold;
		this.edges = new ArrayList<Edge>();
	}
	
	/**
	 * complete constructor, creates node, initializes variables, adds agent
	 * @param id a node has to have an id
	 * @param data generic data a node may contain
	 * @param gold the amount of gold the node contains
	 * @param agent the agent positioned on the node
	 */
	public Node (Integer id, Integer x, Integer y, Integer gold, ArrayList<IAgentDescription> agent){
		this.id = id;
		this.x = x;
		this.y = y;
		this.gold = gold;
		this.agents = agent;
		this.edges = new ArrayList<Edge>();
	}
	
	@Override
	public boolean equals (Object obj){
		return ((this.id == null) || (obj instanceof Node && ((Node)obj).getID() == this.getID()));
	}
	
	@Override
	public int hashCode() {
		return System.identityHashCode(this);
	}
	
	@Override
	public String toString(){
		String str = "Node ID: " + id + " - coords: (" + x + "," + y + ") - gold: " + gold + " - " + agents.size() + " agent(s): ";
		if(!agents.isEmpty())
			for (IAgentDescription a : agents)
				str += a.getAid() + "   "; 
		return str;
	}
	
	/**
	 * standard getter for node gold
	 * @return amount of gold the node contains
	 */
	public Integer getGold() {
		return gold;
	}

	/**
	 * standard setter for node gold
	 * @param gold amount of gold the node will contain
	 */
	public void setGold (int gold) throws NumberFormatException{
		if(gold < 0)
			throw new NumberFormatException("Gold values must be positive");
		else
			this.gold = gold;
	}
	
	/**
	 * standard getter for x coordinate
	 * @return
	 */
	public int getX() {
		return x;
	}

	/**
	 * standard getter for y coordinate
	 * @return
	 */
	public int getY() {
		return y;
	}

	/**
	 * standard getter for node ID
	 * @return
	 */
	public int getID(){
		return id;
	}
	
	/**
	 * Method to get neighbors of the node
	 * @return ArrayList of Neighbor-nodes
	 */
	public ArrayList<Node> getNeighbors(){
		ArrayList<Node> neighbors = new ArrayList<Node>();
		for(Edge e : edges){
			neighbors.add(e.getTarget());
		}
		return neighbors;
	}
	
	/**
	 * Checks if a node is a neighbor of this node. Another node is a neighbor of this node exactly if an edge to the other node exists.
	 * @param other other node to check
	 * @return true if the nodes are neighbors
	 */
	public boolean isNeighbor(Node other) {
		for (Edge edge: edges) {
			if (edge.getTarget().getID() == other.getID()) {
				return true;
			}
		}
		return false;
	}
	
	/**
	 * Adds an edge to the node 
	 * @param weight weight of the edge
	 * @param node target node of the edge
	 */
	public void addEdge(int weight, Node node) throws IllegalArgumentException{
		if (node.equals(this)){
			throw new IllegalArgumentException("Edges with same source and target are not supported.");
		}
		if (weight < 0){
			throw new IllegalArgumentException("Negative weights are not supported.");
		}
		edges.add(new Edge(weight, node));
	}

	/**
	 * Adds an edge to the node 
	 * @param weight weight of the edge
	 * @param node target node of the edge
	 * @param decay pheromone decay factor
	 */
	public void addEdge(int weight, Node node, double decay) throws IllegalArgumentException{
		if (node.equals(this)){
			throw new IllegalArgumentException("Edges with same source and target are not supported.");
		}
		if (weight < 0){
			throw new IllegalArgumentException("Negative weights are not supported.");
		}
		edges.add(new Edge(weight, node, decay));
	}

	
	/**
	 * Method to get all edges
	 * @return ArrayList of all edges
	 */
	public ArrayList<Edge> getEdges() {
		return edges;
	}
	
	/**
	 * Removes a specific edge to the given target node
	 * @param target node of edge to delete
	 */
	public void removeEdge(Node node) throws IllegalArgumentException{
		boolean deleted = false;
		for(Edge e : edges){
			if(e.getTarget().equals(node)){
				edges.remove(e);
				deleted = true;
				break;
			}
		}
		if(!deleted)
			throw new IllegalArgumentException("Edge does not exist!");
	}
	
	
	
}
